﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Audio;

namespace Najak
{
	public partial class Entity
	{

		public int TeamID;

		public List<EntityAnimation> Effects = new List<EntityAnimation>();

		public bool IsInvulnerable;

		public Texture2D Texture { get { return _texture; } }
		protected Texture2D _texture;

		public Vector2 Origin { get { return _origin; } }
		protected Vector2 _origin;
		protected SoundEffect _destroyedSound;

		public Color Color = Color.White;
		public bool IsDestructible = true;
		public bool IsSelectable = true;

		public string Name;

		public bool IsCollidable = true;

		private Vector2 _baseLocation;

		public Scene Scene { get { return _scene; } }
		internal Scene _scene;

		public Vector2 LocationOffset;
		public float Scale = 1;

		public Vector2 Location
		{
			get { return _baseLocation + LocationOffset; }
			set { _baseLocation = value - LocationOffset; }
		}

		public int BoundingRadius
		{
			get { return _boundingRadius; }
			set
			{
				_boundingRadius = value;
				BoundingRadius2 = value * value;
			}
		}
		private int _boundingRadius;

		public int BoundingRadius2 { get; private set; }

		public Entity()
		{
		}

		protected bool _wasInitialized { get; private set; }

		public void EnsureInitialized()
		{
			if (_wasInitialized)
				return; // already constructed

			try
			{
				OnInitializing();
				OnInitialize();
				OnInitialized();
			}
			finally
			{
				_wasInitialized = true;
			}

		}
		protected virtual void OnInitialize() { }
		protected virtual void OnInitializing() { }
		protected virtual void OnInitialized() { }

		public void LoadContent()
		{
			if (_wasContentLoaded)
				return; // already loaded

			if (Scene == null && !App.IsEditorMode)
				return; // can't load content until we're in a scene

			try
			{
				OnLoadContent();
			}
			finally
			{
				_wasContentLoaded = true;
			}
		}

		protected bool _wasContentLoaded { get; private set; }

		protected virtual void OnLoadContent()
		{
			_texture = Scene.Content.Load<Texture2D>("Entities/Barrel");
			_origin = new Vector2(_texture.Width / 2.0f, _texture.Height / 2.0f);
			_destroyedSound = Scene.Content.Load<SoundEffect>("Sounds/PlayerKilled");
		}

		protected void UnloadContent()
		{
			if (!_wasContentLoaded)
				return; // nothing to unload

			try
			{
				OnUnloadContent();
			}
			finally
			{
				_wasContentLoaded = false;
			}
		}
		protected virtual void OnUnloadContent()
		{
			_texture = null;
			_origin = Vector2.Zero;
			_destroyedSound = null;
		}

		public virtual Entity Clone()
		{
			EnsureInitialized();
			Entity clone = this.MemberwiseClone() as Entity;
			return clone;
		}

		public virtual bool AddToScene(Scene scene, Vector2 location)
		{
			if (!_wasInitialized)
				OnInitialize(); // ensure it is constructed

			if (scene == Scene)
				return false; // already add

			if (Scene != null)
			{
				RemoveFromScene();
				UnloadContent();
			}

			if (scene != null)
			{
				OnAddingToScene(scene, location);

				_scene = scene;
				_baseLocation = location;
				BoundingRadius = Tile.Width / 3;

				scene.Entities.Add(this);

				OnAddedToScene();

				OnLoadContent();
				return true;
			}
			return false;
		}
		protected virtual void OnAddingToScene(Scene scene, Vector2 location) { }
		protected virtual void OnAddedToScene() { }

		public virtual bool RemoveFromScene()
		{
			if (Scene == null)
				return false; // was never in a scene

			int index = this.Scene.Entities.IndexOf(this);
			if (index < 0)
				return false; // error!!??

			OnRemovingFromScene();
			OnRemoveFromScene();
			Scene.Entities.RemoveAt(index);
			Scene scene = Scene;
			_scene = null;
			OnRemovedFromScene(scene);
			return true;
		}
		protected virtual void OnRemoveFromScene() { }
		protected virtual void OnRemovingFromScene() { }
		protected virtual void OnRemovedFromScene(Scene scene) { }


		public void Dispose()
		{
			RemoveFromScene();
			UnloadContent();
		}

		public float Health
		{
			get { return _health; }
			set
			{
				if (_health != value)
				{
					_health = value;
					OnHealthChanged();
				}
			}
		}

		private bool _ProcessTileCollisions()
		{
			Rectangle bounds = new Rectangle((int)Location.X - BoundingRadius, (int)Location.Y - BoundingRadius, BoundingRadius * 2, BoundingRadius * 2);
			int leftTile = (int)Math.Floor((float)bounds.Left / Tile.Width);
			int rightTile = (int)Math.Ceiling(((float)bounds.Right / Tile.Width)) - 1;
			int topTile = (int)Math.Floor((float)bounds.Top / Tile.Height);
			int bottomTile = (int)Math.Ceiling(((float)bounds.Bottom / Tile.Height)) - 1;

			bool canCollideX, canCollideY;
			bool hasYCollided = false;
			bool hasXCollided = false;

			// For each potentially colliding tile,
			for (int y = topTile; y <= bottomTile; ++y)
			{
				canCollideY = (y == topTile) || (y == bottomTile);
				for (int x = leftTile; x <= rightTile; ++x)
				{
					canCollideX = (x == leftTile) || (x == rightTile);

					if (!canCollideX && !canCollideY)
						continue; // no collision possible

					TileCollision collision = Scene.GetCollision(x, y);
					bool isCollision = (collision == TileCollision.Blocked);
					if (this is PlayerEntity)
					{
						PlayerEntity p = this as PlayerEntity;
						if (!p.IsUserControlled)
						{
							isCollision |= (collision == TileCollision.NonPath);
						}
					}
					if (isCollision)
					{
						Tile tile = Scene.GetTile(x, y);
						if (tile != null)
						{
							tile.OnEntityCollided(this);
						}

						// Determine collision depth (with direction) and magnitude.
						Rectangle tileBounds = Scene.GetBounds(x, y);
						Vector2 depth = MathUtil.Rectangles.GetIntersectionDepth(bounds, tileBounds);
						if (depth != Vector2.Zero)
						{
							Vector2 collisionDirection = Location - new Vector2(tileBounds.Center.X, tileBounds.Center.Y);
							collisionDirection.Normalize();
							int adjacentX = x + Math.Sign(collisionDirection.X);
							int adjacentY = y + Math.Sign(collisionDirection.Y);
							TileCollision adjacentXCollision = Scene.GetCollision(adjacentX, y);
							TileCollision adjacentYCollision = Scene.GetCollision(x, adjacentY);

							Vector2 newPos = Location;

							float absDepthX = Math.Abs(depth.X);
							float absDepthY = Math.Abs(depth.Y);

							// Resolve the collision along the shallow axis.
							if ((absDepthY < absDepthX) && canCollideY)
							{   // Resolve the collision along the Y axis.
								if (!hasYCollided)
								{
									if (adjacentYCollision == TileCollision.Blocked && canCollideX)
									{   // this is part of flat surface -- so ignore this collision!
										continue;
									}
									hasYCollided = true;
									// check opening in that direction
									if (adjacentXCollision != TileCollision.Blocked)
									{   // it's open...so move there
										newPos.X += absDepthY * collisionDirection.X;
									}
									newPos.Y += depth.Y;
								}
							}
							else if ((absDepthX < absDepthY) && canCollideX) // must be X Collide
							{   // Resolve the collision along the X axis.
								if (!hasXCollided)
								{
									if (adjacentXCollision == TileCollision.Blocked && canCollideY)
									{   // this is part of flat surface -- so ignore this collision!
										continue;
									}
									hasXCollided = true;
									// check opening in that direction
									if (adjacentYCollision != TileCollision.Blocked)
									{   // it's open...so move there
										newPos.Y += absDepthX * collisionDirection.Y;
									}
									newPos.X += depth.X;
								}
							}
							Location = newPos;
						}
					}
				}
			}
			return (hasXCollided || hasYCollided);
		}

		protected virtual bool HandleCollisions(Vector2 prevLocation)
		{
			if (Scene == null)
				return false;
			if (!IsCollidable)
				return false;

			bool hasCollidedWithTile = _ProcessTileCollisions();

			// For each potentially colliding tile,
			bool hasCollided = false;
			if (true) //!hasXCollided && !hasYCollided)
			{
				
				foreach (Entity e in Scene.Entities)
				{
					if (e == this || !e.IsCollidable)
						continue;
					Vector2 offset = e.Location - this.Location;

					float distance = offset.Length();
					float minDistance = e._boundingRadius + this._boundingRadius;

					if (distance < minDistance)
					{
						hasCollided = true;
						if (distance == 0)
							offset = new Vector2(0, 1);  // default to UP

						offset.Normalize();
						float distanceOffset = minDistance - distance;
						Vector2 adjustmentOffset = offset * distanceOffset;
						this.Location -= adjustmentOffset;
					}
				}
			}
			Rectangle bounds = new Rectangle(0, 0, Scene.Width * Tile.Size.X, Scene.Height * Tile.Size.Y);
			float x2 = Location.X;
			float y2 = Location.Y;
			if (x2 > bounds.Width - _boundingRadius)
				x2 = bounds.Width - _boundingRadius;
			if (x2 < _boundingRadius)
				x2 = _boundingRadius;
			if (y2 > bounds.Height - _boundingRadius)
				y2 = bounds.Height - _boundingRadius;
			if (y2 < _boundingRadius)
				y2 = _boundingRadius;
			Location = new Vector2(x2, y2);

			if (hasCollided)
				_ProcessTileCollisions(); // again!

			if (hasCollidedWithTile || hasCollided)
				return true;
			else
				return false;
		}
		private float _health = 10;

		public virtual bool TakeDamage(float damage, Entity entity)
		{
			if (IsInvulnerable)
			{
				return false;
			}
			else
			{
				Health -= damage;
				return true;
			}
		}

		protected virtual void OnHealthChanged()
		{
			if (Health <= 0)
			{
				OnDestroyed();
			}
		}

		public virtual void OnDestroyed()
		{
			Health = 0;
			RemoveFromScene();
		}
		public virtual void ApplyPhysics(GameTime gameTime)
		{
			HandleCollisions(Location);
		}

		public virtual void Update(GameTime gameTime)
		{
			for (int i = Effects.Count - 1; i >= 0; i--)
			{
				EntityAnimation effect = Effects[i];
				effect.Update(gameTime);
			}
			ApplyPhysics(gameTime);
			

		}

		public virtual void PostDraw(SpriteBatch spriteBatch, Vector2 offset)
		{
			if (IsSelected)
			{
				Vector2 loc = Location + offset;
				int rad = (_boundingRadius * 5) >> 2;
				Point locI = new Point((int)Math.Round(loc.X), (int)Math.Round(loc.Y));
				Rectangle selectionSquare = new Rectangle(locI.X - rad, locI.Y - rad, rad * 2, rad * 2);
				Drawing.Shapes.Instance.DrawRectangle(selectionSquare, Color.GreenYellow, 2);
			}
		}

		public virtual void Draw(SpriteBatch spriteBatch, Vector2 offset)
		{
			Vector2 loc = Location + offset;

			if (IsSelected)
			{
				Scene.RegisterForPostDraw(this);
			}

			if (Scale == 1)
			{
				spriteBatch.Draw(_texture, loc, null, Color, 0.0f, _origin, 1.0f, SpriteEffects.None, 0.0f);
			}
			else
			{
				spriteBatch.Draw(_texture, loc, null, Color, 0.0f, _origin, Scale, SpriteEffects.None, 0.0f);
			}
		}

		public float FindDistSquared(Point pos)
		{
			float dx = Location.X - pos.X;
			float dy = Location.Y - pos.Y;
			float dist2 = (dx * dx) + (dy * dy);
			return dist2;
		}


		public float FindDistSquared(Vector2 pos)
		{
			float dx = Location.X - pos.X;
			float dy = Location.Y - pos.Y;
			float dist2 = (dx * dx) + (dy * dy);
			return dist2;
		}

		public bool IsSelected { get; private set; }

		protected virtual void OnSelected()
		{
			IsSelected = true;
			//Game.ShowMessage(5, "Selected:" + this); 
		}
		protected virtual void OnDeselected()
		{
			IsSelected = false;
		}

	}
}
